package nachos.threads;

import nachos.ag.BoatGrader;
import nachos.machine.Lib;
import nachos.machine.Machine;

/* BASIC Algorithm
if(no child on Molokai)
{
	move two children to Molokai
	return one children to Oahu from Molokai
}
else if(at least one child on Molokai)
{
	if(at least one adult is on Oahu)
	{
		move one adult to Molokai
		return one child to Oahu from Molokai
	}
	else if(no adult is on Oahu)
	{
		move two children to Molokai
		if(no one is on Oahu)
			return (FINISHED)
		return one children to Oahu from Molokai
	}
}
*/

public class Boat {
	static BoatGrader bg;

	public static void selfTest() {
		BoatGrader b = new BoatGrader();

		//System.out.println("\n ***Testing Boats with only 2 children***");
		//begin(0, 2, b);

		//System.out.println("\n ***Testing Boats with 2 children, 1 adult***");
		//begin(1, 2, b);

		// System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
		begin(3, 3, b);
	}

	public static void begin(int adults, int children, BoatGrader b) {
		// Store the externally generated autograder in a class
		// variable to be accessible by children.
		bg = b;

		// Instantiate global variables here
		lock = new Lock();
		OahuWaitingAdults = new Condition(lock);
		OahuWaitingChildren = new Condition(lock);
		MolokaiWaitingChildren = new Condition(lock);
		boatLock = new Condition(lock);
		
		boatLocation = OAHU;
		
		numInitAdultsOnOahu = adults;
		numInitChildrenOnOahu = children;
		signal = new Communicator();
		

		// Create threads here. See section 3.4 of the Nachos for Java
		// Walkthrough linked from the projects page.
		
		// boat Thread
		//Runnable rBoat = new Runnable() {
		//	public void run() {
		//		Lib.debug(dbgPeter, "[+][Boat] Boat fork!");
		//		BoatItinerary();
		//	}
		//};
		//boat = new KThread(rBoat);
	    //boat.setName("Boat").fork();
		
		// Adults Threads
		KThread tAdults[] = new KThread[adults];
		Runnable rAdults = new Runnable() {
			public void run() {
				Lib.debug(dbgPeter, "[+][Boat] Adult fork!");
				AdultItinerary();
			}
		};
	    for(int i=0; i<adults; i++)
	    {
	    	tAdults[i] = new KThread(rAdults);
		    tAdults[i].setName("Adult").fork();
	    }
	    
	    // Children Threads
		KThread tChildren[] = new KThread[children];
		Runnable rChildren = new Runnable() {
			public void run() {
				Lib.debug(dbgPeter, "[+][Boat] Children fork!");
				ChildItinerary();
			}
		};
	    for(int i=0; i<children; i++)
	    {
	    	tChildren[i] = new KThread(rChildren);
		    tChildren[i].setName("ChildOnOahu").fork();
	    }
	    
		
		Runnable r = new Runnable() {
			public void run() {
				int status = 0;

				int ret = signal.listen();
					
				Lib.debug(dbgPeter, "[+][Boat] Communicator Return: " + ret);

				if(ret > 0)
				{
					status += FINISHEDCHILDREN;
					Lib.debug(dbgPeter, "[+][Boat] All Children are"
							+ " carried to Molokai.");
				}

				if(ret > 0)
					System.out.println("\n ***Everyone piles on the boat and goes to Molokai***");
			}
		};
		KThread t = new KThread(r);
		t.setName("Sample Boat Thread");
		t.fork();
	}

	static void AdultItinerary() {
		/*
		 * This is where you should put your solutions. Make calls to the
		 * BoatGrader to show that it is synchronized. For example:
		 * bg.AdultRowToMolokai(); indicates that an adult has rowed the boat
		 * across to Molokai
		 */
		lock.acquire();
		
		/* BASIC Algorithm
		else if(at least one child on Molokai)
		{
			if(at least one adult is on Oahu)
			{
				move one adult to Molokai
				return one child to Oahu from Molokai
			}
		}
		*/
		while(numChildrenOnMolokai == 0 || boatLocation == MOLOKAI || onBoat != 0)
		{
			if(boatLocation == OAHU)
				OahuWaitingChildren.wake();
			else if(boatLocation == MOLOKAI)
				MolokaiWaitingChildren.wake();
			
			OahuWaitingAdults.sleep();
		}
		
		Lib.debug(dbgPeter, "[+][Boat] move adult to Molokai");
		// move to MOLOKAI
		bg.AdultRowToMolokai();
		numAdultsOnMolokai++;
		boatLocation = MOLOKAI;
		MolokaiWaitingChildren.wake();
		
		lock.release();
	}

	
	
	/* BASIC Algorithm
	if(no child on Molokai)
	{
		move two children to Molokai
		return one children to Oahu from Molokai
	}
	else if(at least one child on Molokai)
	{
		if(at least one adult is on Oahu)
		{
			move one adult to Molokai
			return one child to Oahu from Molokai
		}
		else if(no adult is on Oahu)
		{
			move two children to Molokai
			if(no one is on Oahu)
				return (FINISHED)
			return one children to Oahu from Molokai
		}
	}
	*/
	static void ChildItinerary() {
		lock.acquire();
		
		while(status != FINISHED)
		{
			//System.out.println("###while STARTING" + numChildrenOnMolokai);
			if(boatLocation == OAHU)
			{
				Lib.debug(dbgPeter, "[+][Boat] IF OAHU");
				if(numChildrenOnMolokai == 0 && onBoat == 0)
				{	//if(no child on Molokai) move two children to Molokai
					Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, load one child");
					onBoat++;
					boat = KThread.currentThread();
					OahuWaitingChildren.wake();
					boatLock.sleep();
				}
				else if(numChildrenOnMolokai == 0 && onBoat == 1)
				{//if(no child on Molokai) move two children to Molokai
					Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, load second child");
					onBoat++;
					
					boatLocation = MOLOKAI;
					boatUnload = true;
					
					Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, unload second child");
					bg.ChildRowToMolokai();
					KThread.currentThread().setName("ChildOnMolokai");
					numChildrenOnMolokai++;
					
					onBoat--;
					boatLock.wake();
					MolokaiWaitingChildren.sleep();
				}
				else if((numInitAdultsOnOahu-numAdultsOnMolokai) == 0 && (numInitChildrenOnOahu-numChildrenOnMolokai) == 2)
				{
					Lib.debug(dbgPeter, "[+][Boat] just two children left. sail to end");
					if(onBoat == 0)
					{	//if(no child on Molokai) move two children to Molokai
						Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, load one child");
						onBoat++;
						boat = KThread.currentThread();
						OahuWaitingChildren.wake();
						boatLock.sleep();
					}
					else if(onBoat == 1)
					{//if(no child on Molokai) move two children to Molokai
						Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, load second child");
						onBoat++;
						
						boatLocation = MOLOKAI;
						boatUnload = true;
						
						Lib.debug(dbgPeter, "[+][Boat] no child on Molokai, unload second child");
						bg.ChildRowToMolokai();
						KThread.currentThread().setName("ChildOnMolokai");
						numChildrenOnMolokai++;
						
						onBoat--;
						boatLock.wake();
						MolokaiWaitingChildren.sleep();
					}
				}
				else if(numChildrenOnMolokai != 0 && (numInitAdultsOnOahu-numAdultsOnMolokai) == 0 && (numInitChildrenOnOahu-numChildrenOnMolokai) == 1)
				{
					Lib.debug(dbgPeter, "[+][Boat] just one child left. sail to end" + numChildrenOnMolokai);
					bg.ChildRowToMolokai();
					boatLocation = MOLOKAI;
					KThread.currentThread().setName("ChildOnMolokai");
					numChildrenOnMolokai++;
					
					MolokaiWaitingChildren.wake();
					MolokaiWaitingChildren.sleep();
				}
				else if(numChildrenOnMolokai != 0 && (numInitAdultsOnOahu-numAdultsOnMolokai) != 0)
				{
					Lib.debug(dbgPeter, "[+][Boat] wake Adult, there is at least one child on Molokai");
					OahuWaitingAdults.wake();
					OahuWaitingChildren.sleep();
				}
				else
					OahuWaitingChildren.sleep();
				
			}
			else if(boatLocation == MOLOKAI)
			{
				if(boatUnload == true)
				{
					Lib.debug(dbgPeter, "[+][Boat] pilot unload" + numChildrenOnMolokai);
					bg.ChildRideToMolokai();
					boatLocation = MOLOKAI;
					KThread.currentThread().setName("ChildOnMolokai");
					numChildrenOnMolokai++;
					
					onBoat--;
					boatUnload = false;
					
					MolokaiWaitingChildren.wake();
					MolokaiWaitingChildren.sleep();
				}
				else if(boatUnload == false && (numInitAdultsOnOahu-numAdultsOnMolokai) == 0 && (numInitChildrenOnOahu-numChildrenOnMolokai) == 0)
				{	// END ROUTINE
					Lib.debug(dbgPeter, "[+][Boat] END");
					status = FINISHED;
					signal.speak(FINISHEDCHILDREN);
					MolokaiWaitingChildren.sleep();
				}
				else if(boatUnload == false)
				{
					Lib.debug(dbgPeter, "[+][Boat] move one child to Oahu");
					bg.ChildRowToOahu();
					boatLocation = OAHU;
					KThread.currentThread().setName("ChildOnOahu");
					numChildrenOnMolokai--;
					OahuWaitingChildren.wake();
					OahuWaitingChildren.sleep();
				}
				else
					MolokaiWaitingChildren.sleep();
			}
		}
		
		lock.release();
	}

	static void SampleItinerary() {
		// Please note that this isn't a valid solution (you can't fit
		// all of them on the boat). Please also note that you may not
		// have a single thread calculate a solution and then just play
		// it back at the autograder -- you will be caught.
		System.out.println("\n ***Everyone piles on the boat and goes to Molokai***");
		bg.AdultRowToMolokai();
		bg.ChildRowToMolokai();
		bg.ChildRowToOahu();
		bg.ChildRideToMolokai();
	}
	
	private static final char dbgPeter = 'P';
	
	private static int OAHU = 0;
	private static int MOLOKAI = 1;
	private static int FINISHEDCHILDREN = 2;
	private static int FINISHED = 3;
	private static int status = 0;
	private static int onBoat = 0;
	
	private static Lock lock;
	private static Condition OahuWaitingAdults;
	private static Condition OahuWaitingChildren;
	private static Condition MolokaiWaitingChildren;
	private static Condition boatLock;
	
	private static int boatLocation;
	private static boolean boatUnload=false;
	private static KThread boat = null;
	
	private static int numInitAdultsOnOahu = 0;
	private static int numInitChildrenOnOahu = 0;
	private static int numAdultsOnMolokai = 0;
	private static int numChildrenOnMolokai = 0;
	private static Communicator signal;
}
